package com.yuqih.common.exception;

import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.javassist.Modifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.io.Resource;
import org.springframework.util.ReflectionUtils;

import com.fasterxml.jackson.core.type.TypeReference;
import com.yuqih.common.Global;
import com.yuqih.common.annotation.ObjectCreator;
import com.yuqih.common.annotation.ObjectCreatorParam;
import com.yuqih.common.dto.ValueContainer;

/**
 * 业务异常，错误码应该是可处理的，共6位<br>
 * 
 * 将错误码定义为000000(未知异常)和6位数的异常码，1xxxxx为可直接提示给最终用户的异常，2xxxxx为系统可恢复异常（系统状态异常），
 * 3xxxxx为系统不可恢复异常（比如数据库连接失败,io错误等等）
 * 
 * @author yuqih
 * 
 */
public class CodedException extends RuntimeException {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private static final Logger logger = LoggerFactory.getLogger(CodedException.class);

	public static final String UNKNOWN_CODE = "000000";

	private static final String configurationResource = "classpath:settings/exception.conf";
	
	private static final ConfigurableConversionService conversionService;

	private final String code;

	static {
		Resource resource = Global.resourcePatternResolver.getResource(configurationResource);
		ConfigurableConversionService service;
		if (!resource.exists()) {
			service = null;
		} else {
			service = new GenericConversionService();
			try {
				configure(resource, service);
				logger.info("初始化CodedException成功，使用配置：{}" , configurationResource);
			} catch (Exception e) {
				logger.warn("初始化CodedException失败，使用配置：{}" , configurationResource, e);
				service = null;
			}
		}

		conversionService = service;
	}

	@SuppressWarnings("unchecked")
	private static void configure(Resource resource, ConfigurableConversionService service) throws Exception {
		InputStream ins;
		ins = resource.getInputStream();
		List<ExceptionMapping> list = Global.objectMapper.readValue(ins, new TypeReference<List<ExceptionMapping>>() {
		});

		for (ExceptionMapping mapping : list) {
			Class<?> sourceType = Class.forName(mapping.getSourceType());
			if (!Throwable.class.isAssignableFrom(sourceType)) {
				throw new IllegalArgumentException(sourceType + "不是异常类型");
			}
			
			if (!StringUtils.isBlank(mapping.getFactoryType())) {
				CreatorConverterBuilder builder = new CreatorConverterBuilder(mapping);
				builder.build(service, (Class<? extends Throwable>) sourceType);
			} else {
				ConstructConverterBuilder builder = new ConstructConverterBuilder(mapping);
				builder.build(service, (Class<? extends Throwable>) sourceType);
			}
		}
		
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void configure(ICodedExceptionConverter<?> converter) {
		Class<? > sourceType = GenericTypeResolver.resolveTypeArgument(converter.getClass(), ICodedExceptionConverter.class);
		if (sourceType == null) {
			throw new IllegalArgumentException("不能解析异常转换类");
		}
		ICodedExceptionConverter convert = converter;
		conversionService.addConverter((Class<? extends Throwable>)sourceType, CodedException.class, new Converter<Throwable, CodedException>() {

			@Override
			public CodedException convert(Throwable source) {
				return convert.convert(source);
			}
		});
		
	}

	public static CodedException from(Throwable e) {
		Throwable ex = e;
		do {
			if (ex instanceof CodedException) {
				return (CodedException) ex;
			}
			if (ex.getCause() == ex) {
				break;
			}
			ex = ex.getCause();
		} while (ex != null);
		
		if (conversionService == null) {
			return new CodedException(e);
		}
		return conversionService.convert(e, CodedException.class);
	}

	/**
	 * @param message
	 */
	public CodedException() {
		super();
		code = UNKNOWN_CODE;
	}

	/**
	 * @param message
	 */
	public CodedException(String message) {
		super(message);
		code = UNKNOWN_CODE;
	}

	/**
	 * @param e
	 */
	protected CodedException(Throwable e) {
		super(e.getMessage(), e);
		code = UNKNOWN_CODE;
	}

	/**
	 * @param message
	 */
	public CodedException(String code, String message) {
		super(message);
		this.code = code;
	}

	/**
	 * @param message
	 * @param e
	 */
	public CodedException(String code, String message, Throwable e) {
		super(message, e);
		this.code = code;
	}

	/**
	 * @return the code
	 */
	public final String getCode() {
		return code;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "CodedException [code=" + code + ", message=" + getMessage() + "]";
	}

	@Override
	public void printStackTrace() {
		Throwable e = getCause();
		if (e != null) {
			e.printStackTrace();
		} else {
			super.printStackTrace();
		}
	}

	@Override
	public void printStackTrace(PrintStream s) {
		Throwable e = getCause();
		if (e != null) {
			e.printStackTrace(s);
		} else {
			super.printStackTrace(s);
		}
	}

	@Override
	public void printStackTrace(PrintWriter s) {
		Throwable e = getCause();
		if (e != null) {
			e.printStackTrace(s);
		} else {
			super.printStackTrace(s);
		}
	}
	
	private static class ConstructConverterBuilder {
		
		private final ExceptionMapping mapping;

		/**
		 * @param mapping
		 */
		ConstructConverterBuilder(ExceptionMapping mapping) {
			super();
			this.mapping = mapping;
		}
		
		void build(ConfigurableConversionService service, Class<? extends Throwable> sourceType) {
			String code = mapping.getCode();
			if (StringUtils.isBlank(code)) {
				throw new IllegalArgumentException("定义的异常编码不能为空");
			}
			ConstructConverter converter = new ConstructConverter(code, mapping.getMessage());
			service.addConverter(sourceType, CodedException.class, converter);
		}
		
	}
	
	private static class ConstructConverter implements Converter<Throwable, CodedException> {
		
		private final String code;
		private final String message;
		
		/**
		 * @param code
		 * @param message
		 */
		ConstructConverter(String code, String message) {
			super();
			this.code = code;
			this.message = message;
		}

		@Override
		public CodedException convert(Throwable source) {
			String message = this.message;
			if (message == null) {
				message = source.getMessage();
			}
			return new CodedException(code, message, source);
		}
	}
	
	private static class CreatorConverterBuilder {
		private final ExceptionMapping mapping;

		/**
		 * @param mapping
		 */
		CreatorConverterBuilder(ExceptionMapping mapping) {
			super();
			this.mapping = mapping;
		}
		
		void build(ConfigurableConversionService service, Class<? extends Throwable> sourceType) throws Exception {
			Class<?> factoryType = Class.forName(mapping.getFactoryType());
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(factoryType);
			Method creatorMethod = null;
			for (Method method : methods) {
				if (!Modifier.isStatic(method.getModifiers())) {
					continue;
				}

				if (method.getAnnotation(ObjectCreator.class) != null) {
					creatorMethod = method;
					break;
				}
			}
			
			if (creatorMethod == null) {
				throw new IllegalArgumentException("未定义工厂方法");
			}
			
			if (!CodedException.class.isAssignableFrom(creatorMethod.getReturnType())) {
				throw new IllegalArgumentException("工厂方法返回值类型错误");
			}

			Parameter[] parameters = creatorMethod.getParameters();
			List<ValueContainer<Object>> params = new ArrayList<>();
			for (Parameter param : parameters) {
				//param.get
				ObjectCreatorParam ocp = param.getAnnotation(ObjectCreatorParam.class);
				if (ocp == null) {
					throw new IllegalArgumentException("工厂方法的参数必须标注对应参数");
				}
				
				if (StringUtils.equals( ocp.value(), "source")) {
					 if (!param.getType().isAssignableFrom(sourceType) 
							 ) {
						 throw new IllegalArgumentException("source参数类型错误");
					 }
					 params.add(null);
				} else if (StringUtils.equals( ocp.value(), "code")) {
					if (!param.getType().isAssignableFrom(String.class) 
							 ) {
						 throw new IllegalArgumentException("code参数类型错误");
					 }
					 params.add(new ValueContainer<Object>(mapping.getCode()) );
				} else if (StringUtils.equals( ocp.value(), "message")) {
					if (!param.getType().isAssignableFrom(String.class) 
							 ) {
						 throw new IllegalArgumentException("message参数类型错误");
					 }
					params.add(new ValueContainer<Object>(mapping.getMessage()));
				} else {
					throw new IllegalArgumentException("无对应参数" + ocp.value());
				}
			}
			
			CreatorConverter converter = new CreatorConverter(creatorMethod, params);
			service.addConverter(sourceType, CodedException.class, converter);
		}
		
	}
	
	private static class CreatorConverter implements Converter<Throwable, CodedException> {
		
		private final Method creatorMethod;
		private final List<ValueContainer<Object>> params;
		
		/**
		 * @param creatorMethod
		 * @param params
		 */
		CreatorConverter(Method creatorMethod, List<ValueContainer<Object>> params) {
			super();
			this.creatorMethod = creatorMethod;
			this.params = params;
		}

		@Override
		public CodedException convert(Throwable source) {
			try {
				Object[] parameters = new Object[params.size()];
				for (int i = 0; i < params.size(); i++) {
					ValueContainer<Object> c = params.get(i);
					if (c != null) {
						parameters[i] = c.getValue();
					} else {
						parameters[i] = source;
					}
				}
				return (CodedException) creatorMethod.invoke(null, parameters);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				return new CodedException(source);
			}
		}
		
	}

}
